using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace NotepadExpress
{
    public class Document : TabPage
    {
        // CustomEvent Step 3: Define event with type of predefined delegate above
        public event CustomSelectionChangedHandler SelectionChanged;
        public event CustomTextChangedHandler DataChanged;

        /// <summary> 
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary> 
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Component Designer generated code

        /// <summary> 
        /// Required method for Designer support - do not modify 
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Document));
            this.printDoc = new System.Drawing.Printing.PrintDocument();
            this.dlgOpen = new System.Windows.Forms.OpenFileDialog();
            this.dlgSave = new System.Windows.Forms.SaveFileDialog();
            this.rtbCtrl = new RichTextBoxPrintCtrl();
            this.dlgPrint = new System.Windows.Forms.PrintDialog();
            this.dlgPageSetup = new System.Windows.Forms.PageSetupDialog();
            this.dlgPreview = new System.Windows.Forms.PrintPreviewDialog();
            this.SuspendLayout();
            // 
            // printDoc
            // 
            this.printDoc.PrintPage += new System.Drawing.Printing.PrintPageEventHandler(this.printDoc_PrintPage);
            this.printDoc.BeginPrint += new System.Drawing.Printing.PrintEventHandler(this.printDoc_BeginPrint);
            // 
            // dlgOpen
            // 
            this.dlgOpen.FileName = "";
            // 
            // rtbCtrl
            // 
            this.rtbCtrl.Dock = System.Windows.Forms.DockStyle.Fill;
            this.rtbCtrl.Font = new System.Drawing.Font("Courier New", 9F);
            this.rtbCtrl.Location = new System.Drawing.Point(0, 0);
            this.rtbCtrl.Name = "rtbCtrl";
            this.rtbCtrl.Size = new System.Drawing.Size(200, 100);
            this.rtbCtrl.TabIndex = 0;
            this.rtbCtrl.Text = "";
            this.rtbCtrl.WordWrap = false;
            this.rtbCtrl.SelectionChanged += new System.EventHandler(this.rtbCtrl_SelectionChanged);
            this.rtbCtrl.TextChanged += new System.EventHandler(this.rtbCtrl_TextChanged);
            // 
            // dlgPrint
            // 
            this.dlgPrint.Document = this.printDoc;
            this.dlgPrint.UseEXDialog = true;
            // 
            // dlgPageSetup
            // 
            this.dlgPageSetup.Document = this.printDoc;
            // 
            // dlgPreview
            // 
            this.dlgPreview.AutoScrollMargin = new System.Drawing.Size(0, 0);
            this.dlgPreview.AutoScrollMinSize = new System.Drawing.Size(0, 0);
            this.dlgPreview.ClientSize = new System.Drawing.Size(780, 560);
            this.dlgPreview.Document = this.printDoc;
            this.dlgPreview.Enabled = true;
            this.dlgPreview.Name = "dlgPreview";
            this.dlgPreview.Visible = false;
            // 
            // Document
            // 
            this.Controls.Add(this.rtbCtrl);
            this.Name = "Document";
            this.Text = "Untitled";
            this.ResumeLayout(false);

        }

        #endregion

        private RichTextBoxPrintCtrl rtbCtrl;
        private System.Drawing.Printing.PrintDocument printDoc;
        private OpenFileDialog dlgOpen;
        private SaveFileDialog dlgSave;
        private PrintDialog dlgPrint;
        private PageSetupDialog dlgPageSetup;
        private PrintPreviewDialog dlgPreview;

        private string fileName = string.Empty;
        private bool dirty = false;
        private int lineNumber, columnNumber;
        private int checkPrint;
        private int numChars;

        public Document()
        {
            InitializeComponent();
        }

        private void printDoc_BeginPrint(object sender, System.Drawing.Printing.PrintEventArgs e)
        {
            checkPrint = 0;
        }

        private void printDoc_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
        {
            // Print the content of RichTextBox. Store the last character printed.
            checkPrint = rtbCtrl.Print(checkPrint, rtbCtrl.TextLength, e);

            // Check for more pages
            if (checkPrint < rtbCtrl.TextLength)
                e.HasMorePages = true;
            else
                e.HasMorePages = false;
        }

        private void rtbCtrl_SelectionChanged(object sender, EventArgs e)
        {
            lineNumber = GetLineNumber(rtbCtrl);
            columnNumber = GetColumnNumber(rtbCtrl);

            // CustomEvent Step 4: Define new event argument and call management function
            OnSelectionChanged(new CustomSelectionChangedEventArgs(lineNumber, columnNumber));
        }

        // CustomEvent Step 5: Define virtual function to manage the event
        protected virtual void OnSelectionChanged(CustomSelectionChangedEventArgs e)
        {
            // CustomEvent Step 6: Fire event
            if (SelectionChanged != null)
                SelectionChanged(this, e);
        }

        private void rtbCtrl_TextChanged(object sender, EventArgs e)
        {
            /* Bug: TextChanged is fired when font changed too, 
             * use special var to count number of chars as a
             * workaround
             */
            if (numChars != rtbCtrl.Text.Length)
            {
                dirty = true;
                numChars = rtbCtrl.Text.Length;
                OnDataChanged(new EventArgs());
            }
        }

        protected virtual void OnDataChanged(EventArgs e)
        {
            if (DataChanged != null)
                DataChanged(this, e);
        }

        private int GetLineNumber(RichTextBoxPrintCtrl rtb)
        {
            return rtb.GetLineFromCharIndex(rtb.SelectionStart) + 1;
        }

        private int GetColumnNumber(RichTextBoxPrintCtrl rtb)
        {
            int LineNumber = rtb.GetLineFromCharIndex(rtb.SelectionStart);
            int ColNumber = rtb.GetFirstCharIndexFromLine(LineNumber);
            return rtb.SelectionStart - ColNumber + 1;

            /* Bug: if rtb's wordwrap is on GetLineFromCharIndex will return 
             * wrapped line number whilst Lines collection return unwrapped lines.
             */
            //int LineNumber = rtb.GetLineFromCharIndex(rtb.SelectionStart);
            //int LineOffset = 0;
            //int i = 0;
            //foreach (string Line in rtb.Lines)
            //{
            //    if (i < LineNumber)
            //        LineOffset += Line.Length + 1;
            //    else
            //        break;

            //    i++;
            //}
            //return rtb.SelectionStart - LineOffset + 1;
        }

        /// <summary>
        /// Return current column
        /// </summary>
        /// <returns></returns>
        public int ColumnNumber()
        {
            return columnNumber;
        }

        /// <summary>
        /// Return current line
        /// </summary>
        /// <returns></returns>
        public int LineNumber()
        {
            return lineNumber;
        }

        /// <summary>
        /// Return file name of active document
        /// </summary>
        public string FileName
        {
            get { return fileName; }
            set { fileName = value; }
        }

        /// <summary>
        /// Return updating status of active document
        /// </summary>
        public bool Dirty
        {
            get { return dirty; }
            set { dirty = value; }
        }

        /// <summary>
        /// Clear active document's content
        /// </summary>
        public void NewDocument()
        {
            // Clear rich text box
            rtbCtrl.Clear();
            fileName = string.Empty;
            Text = Properties.Resources.DocumentName;
            numChars = 0;
            dirty = false;
        }

        /// <summary>
        /// Open the document using open file dialog
        /// </summary>
        public void OpenDocument()
        {
            // Initialize dialog
            dlgOpen.Filter = Properties.Resources.OpenFilter;

            // Use previous file's path as initial directory
            if (fileName.Length > 0)
            {
                dlgOpen.InitialDirectory = fileName.Substring(0, fileName.LastIndexOf(@"\"));
            }

            // Show dialog
            if (dlgOpen.ShowDialog() == DialogResult.OK)
            {
                fileName = dlgOpen.FileName;
                rtbCtrl.LoadFile(fileName, RichTextBoxStreamType.PlainText);
                Text = fileName;
                numChars = rtbCtrl.Text.Length;
                dirty = false;
            }
        }

        /// <summary>
        /// Save current file
        /// </summary>
        public void SaveDocument()
        {
            // Save now
            if (fileName.Length > 0)
            {
                rtbCtrl.SaveFile(fileName, RichTextBoxStreamType.PlainText);
                numChars = rtbCtrl.Text.Length;
                dirty = false;
            }
        }

        /// <summary>
        /// Save current file as a new one
        /// </summary>
        /// <returns></returns>
        public string SaveDocumentAs()
        {
            // Initialize dialog
            dlgSave.Filter = Properties.Resources.SaveFilter;
            dlgSave.InitialDirectory = dlgOpen.InitialDirectory;

            // Show dialog
            if (dlgSave.ShowDialog() == DialogResult.OK)
            {
                fileName = dlgSave.FileName;
                rtbCtrl.SaveFile(fileName, RichTextBoxStreamType.PlainText);
                Text = fileName;
                numChars = rtbCtrl.Text.Length;
                dirty = false;
                return fileName;
            }
            else
            {
                return string.Empty;
            }
        }

        /// <summary>
        /// Print current document
        /// </summary>
        public void PrintDocument()
        {
            if (dlgPrint.ShowDialog() == DialogResult.OK)
            {
                printDoc.Print();
            }
        }

        /// <summary>
        /// Display preview dialog for current document
        /// </summary>
        public void PreviewDocument()
        {
            dlgPreview.ShowDialog();
        }

        /// <summary>
        /// Display page setup dialog for current document
        /// </summary>
        public void PageSetupDocument()
        {
            dlgPageSetup.ShowDialog();
        }

        /// <summary>
        /// Toggle word wrapping flag of the document
        /// </summary>
        public void ToggleWordWrap(bool wrap)
        {
            rtbCtrl.WordWrap = wrap;
        }

        /// <summary>
        /// Cut selected text
        /// </summary>
        public void CutText()
        {
            rtbCtrl.Cut();
        }

        /// <summary>
        /// Copy selected text to the clipboard
        /// </summary>
        public void CopyText()
        {
            rtbCtrl.Copy();
        }

        /// <summary>
        /// Paste clipboard content onto the rich text box
        /// </summary>
        public void PasteText()
        {
            rtbCtrl.Paste();
        }

        /// <summary>
        /// Delete selected text
        /// </summary>
        public void DeleteText()
        {
            rtbCtrl.SelectedText = string.Empty;
        }

        /// <summary>
        /// Replace selected text with specified string
        /// </summary>
        /// <param name="Text"></param>
        public void ReplaceSelectedText(string Text)
        {
            rtbCtrl.SelectedText = Text;
        }

        /// <summary>
        /// Return can undo status
        /// </summary>
        /// <returns></returns>
        public bool CanUndo()
        {
            return rtbCtrl.CanUndo;
        }

        /// <summary>
        /// Return can redo status
        /// </summary>
        /// <returns></returns>
        public bool CanRedo()
        {
            return rtbCtrl.CanRedo;
        }

        /// <summary>
        /// Undo the last action
        /// </summary>
        public void Undo()
        {
            if (rtbCtrl.CanUndo)
                rtbCtrl.Undo();
        }

        /// <summary>
        /// Redo the last action
        /// </summary>
        public void Redo()
        {
            if (rtbCtrl.CanRedo)
                rtbCtrl.Redo();
        }

        /// <summary>
        /// Select all text
        /// </summary>
        public void SelectAll()
        {
            rtbCtrl.SelectAll();
        }

        /// <summary>
        /// Insert date time at selection point
        /// </summary>
        public void InsertDateTime()
        {
            string sDateTime = System.DateTime.Today.ToString();
            int iLen = sDateTime.Length;
            int iStart = rtbCtrl.SelectionStart;

            if (rtbCtrl.SelectionLength > 0)
                rtbCtrl.Text = rtbCtrl.SelectedText.Remove(0);

            rtbCtrl.Text = rtbCtrl.Text.Insert(iStart, sDateTime);
            rtbCtrl.Select(iStart, iLen);
        }

        /// <summary>
        /// Find specific text within the rich text box control
        /// </summary>
        /// <param name="findText">Text to find</param>
        /// <param name="findForward">Forward or backward</param>
        /// <param name="matchWord">Match the whole word</param>
        /// <param name="matchCase">Match case</param>
        /// <returns>Start position of text found or negative if not</returns>
        public int Find(string findText, bool findForward, bool matchWord, bool matchCase)
        {
            return FindAndReplace(findText, findForward, matchWord, matchCase, false, string.Empty);
        }

        /// <summary>
        /// Find specific text and replace with the new text
        /// </summary>
        /// <param name="findText">Text to find</param>
        /// <param name="replaceText">String to replace</param>
        /// <param name="findForward">Forward or backward</param>
        /// <param name="matchWord">Match the whole word</param>
        /// <param name="matchCase">Match case</param>
        /// <returns>Start position of text replaced or negative if not found</returns>
        public int FindAndReplace(string findText, string replaceText, bool findForward, bool matchWord, bool matchCase)
        {
            return FindAndReplace(findText, findForward, matchWord, matchCase, true, replaceText);
        }

        /// <summary>
        /// Find specific text within the rich text box control
        /// </summary>
        /// <param name="findText">Text to find</param>
        /// <param name="findForward">Forward or backward</param>
        /// <param name="matchWord">Match the whole word</param>
        /// <param name="matchCase">Match case</param>
        /// <param name="replace">Replace flag</param>
        /// <param name="replaceText">String to replace</param>
        /// <returns>Start position of text found or negative if not</returns>
        private int FindAndReplace(string findText, bool findForward, 
            bool matchWord, bool matchCase, 
            bool replace, string replaceText)        
        {
            int iStart = 1;
            int iFound = -1;
            if (rtbCtrl.Text.Length > 0 && findText.Length > 0)
            {
                if (findForward)
                {
                    iStart = rtbCtrl.SelectionStart + 1;
                    if (matchCase)
                        if (matchWord)
                            iFound = rtbCtrl.Find(findText, iStart, 
                                RichTextBoxFinds.MatchCase | RichTextBoxFinds.WholeWord);
                        else
                            iFound = rtbCtrl.Find(findText, iStart, 
                                RichTextBoxFinds.MatchCase);
                    else
                        if (matchWord)
                            iFound = rtbCtrl.Find(findText, iStart, 
                                RichTextBoxFinds.WholeWord);
                        else
                            iFound = rtbCtrl.Find(findText, iStart, 
                                RichTextBoxFinds.None);
                }
                else
                {
                    iStart = rtbCtrl.SelectionStart - 1;
                    if (matchCase)
                        if (matchWord)
                            iFound = rtbCtrl.Find(findText, iStart, 
                                RichTextBoxFinds.MatchCase | RichTextBoxFinds.WholeWord | RichTextBoxFinds.Reverse);
                        else
                            iFound = rtbCtrl.Find(findText, iStart, 
                                RichTextBoxFinds.MatchCase | RichTextBoxFinds.Reverse);
                    else
                        if (matchWord)
                            iFound = rtbCtrl.Find(findText, iStart, 
                                RichTextBoxFinds.WholeWord | RichTextBoxFinds.Reverse);
                        else
                            iFound = rtbCtrl.Find(findText, iStart, 
                                RichTextBoxFinds.Reverse);
                }
                if (replace && iFound >= 0 && rtbCtrl.SelectionLength > 0)
                    rtbCtrl.SelectedText = replaceText;
            }
            return iFound;
        }

        /// <summary>
        /// Activate the rich text box control
        /// </summary>
        public void Activate()
        {
            rtbCtrl.Select();
        }

        /// <summary>
        /// Move text cursor to the first position
        /// </summary>
        public void GoTop()
        {
            rtbCtrl.Select(0, 0);
        }

        /// <summary>
        /// Move text cursor to the last position
        /// </summary>
        public void GoBottom()
        {
            rtbCtrl.Select(rtbCtrl.Text.Length, 0);
        }

        /// <summary>
        /// Select any text specified
        /// </summary>
        /// <param name="start">Start position</param>
        /// <param name="length">Selection length</param>
        public void SelectText(int start, int length)
        {
            rtbCtrl.Select(start, length);
        }

        /// <summary>
        /// Goto specific line number and column
        /// </summary>
        /// <param name="line"></param>
        public void GoTo(int lineNumber, int columnNumber)
        {
            if (lineNumber > 0 && rtbCtrl.Text.Length > 0)
            {
                int i = rtbCtrl.GetFirstCharIndexFromLine(lineNumber - 1);
                if (columnNumber > 0)
                    i += (columnNumber - 1);
                rtbCtrl.Select(i, 0);
            }
            else
            {
                rtbCtrl.Select(columnNumber, 0);
            }
        }

        /// <summary>
        /// Return selected text
        /// </summary>
        /// <returns></returns>
        public string SelectedText
        {
            get { return rtbCtrl.SelectedText; }
        }

        /// <summary>
        /// Return document's font
        /// </summary>
        /// <returns></returns>
        public Font GetFont()
        {
            return rtbCtrl.Font;
        }

        /// <summary>
        /// Set font of document to be specified font
        /// </summary>
        /// <param name="font"></param>
        public void SetFont(Font font)
        {
            rtbCtrl.Font = font;
        }
    }



    // CustomEvent Step 1: Define delegate
    public delegate void CustomSelectionChangedHandler(object sender, CustomSelectionChangedEventArgs e);
    public delegate void CustomTextChangedHandler(object sender, EventArgs e);

    // CustomEvent Step 2: Define class to be used as event argument
    public class CustomSelectionChangedEventArgs : EventArgs
    {
        private int _line;
        private int _col;
        public CustomSelectionChangedEventArgs(int lineNumber, int columnNumber)
        {
            this._line = lineNumber;
            this._col = columnNumber;
        }
        public int LineNumber { get { return _line; } }
        public int ColumnNumber { get { return _col; } }
    }


}
